package qpay

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

var (
	success               = "SUCCESS"
	empty                 = ""
	appID                 = "00000156"
	cusID                 = "008310147890008"
	merchantID            = "008310147890008"
	userRegisterKey       = "1234567890"
	version               = "11"
	requestIP             = "127.0.0.1"
	paymentKey            = "43df939f1e7f5c6909b3f4b63f893994"
	userRegiseterURL      = "https://cashier.allinpay.com/usercenter/merchant/UserInfo/reg.do"
	agreementApplyURL     = "https://vsp.allinpay.com/apiweb/qpay/agreeapply"
	agreementConfirmURL   = "https://vsp.allinpay.com/apiweb/qpay/agreeconfirm"
	agreementSMSResendURL = "https://vsp.allinpay.com/apiweb/qpay/agreesms"
	paymentApplyURL       = "https://vsp.allinpay.com/apiweb/qpay/payapplyagree"
	paymentConfirmURL     = "https://vsp.allinpay.com/apiweb/qpay/payagreeconfirm"
	paymentSMSResendURL   = "https://vsp.allinpay.com/apiweb/qpay/paysmsagree"
	paymentNotifyURL      = "http://m.quickly56.com/v1/api/105008"
)

func Init(env string) {
	if env == "dev" || env == "test" || env == empty {
		cusID = "990581053996001"
		merchantID = "008310107420099"
		userRegiseterURL = "http://116.236.252.102:18085/usercenter/merchant/UserInfo/reg.do"
		agreementApplyURL = "http://test.allinpaygd.com/apiweb/qpay/agreeapply"
		agreementConfirmURL = "http://test.allinpaygd.com/apiweb/qpay/agreeconfirm"
		agreementSMSResendURL = "http://test.allinpaygd.com/apiweb/qpay/agreesms"
		paymentApplyURL = "http://test.allinpaygd.com/apiweb/qpay/payapplyagree"
		paymentConfirmURL = "http://test.allinpaygd.com/apiweb/qpay/payagreeconfirm"
		paymentSMSResendURL = "http://test.allinpaygd.com/apiweb/qpay/paysmsagree"
		paymentNotifyURL = "http://mt.quickly56.com/v1/api/105008"
	}
}

type KV map[string]string

func (kv KV) sign() string {
	var i int
	var text string

	keys := make([]string, len(kv))
	for k := range kv {
		keys[i] = k
		i++
	}
	sort.Strings(keys)
	for _, k := range keys {
		if v := kv[k]; v != empty {
			text += fmt.Sprintf("%s=%s&", k, v)
		}
	}

	return strings.ToUpper(fmt.Sprintf("%x", md5.Sum([]byte(strings.TrimSuffix(text, "&")))))
}

func (kv KV) verify() bool {
	sign := kv["sign"]
	kv["key"] = paymentKey
	delete(kv, "sign")
	return sign == kv.sign()
}

func (kv KV) accept(form url.Values) {
	for k, v := range form {
		if len(v) >= 1 {
			kv[k] = v[0]
		}
	}
}

type ParamV2 struct {
	kv KV
}

func (p *ParamV2) params() KV {
	p.kv = map[string]string{
		"cusid":     cusID,
		"appid":     appID,
		"version":   version,
		"reqip":     requestIP,
		"key":       paymentKey,
		"reqtime":   time.Now().Format("20060102150405"),
		"randomstr": fmt.Sprintf("%d", time.Now().Nanosecond()),
	}
	return p.kv
}

func (p *ParamV2) sign() {
	p.kv["sign"] = p.kv.sign()
}

func (p *ParamV2) post(target string, v interface{}) (string, error) {
	forms := make(url.Values)

	for k, v := range p.kv {
		forms.Add(k, v)
	}

	return do(target, forms, v)
}

type Agreement struct {
	*ParamV2
	MeruserID string
	AcctType  string
	AcctNO    string
	IDNO      string
	AcctName  string
	Mobile    string
	ValidDate string
	Cvv2      string
}

func NewAgreement() *Agreement {
	return &Agreement{ParamV2: new(ParamV2)}
}

func (a *Agreement) Apply() (*AgreementApplyResult, error) {
	a.build()
	a.sign()
	result := &AgreementApplyResult{ResultV2: &ResultV2{kv: make(map[string]string)}}
	undecoded, err := a.post(agreementApplyURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (a *Agreement) Confirm(smscode, thpinfo string) (*AgreementConfirmResult, error) {
	a.build()
	if smscode != empty {
		a.kv["smscode"] = smscode
	}
	if thpinfo != empty {
		a.kv["thpinfo"] = thpinfo
	}
	a.sign()
	result := &AgreementConfirmResult{ResultV2: &ResultV2{kv: make(map[string]string)}}
	undecoded, err := a.post(agreementConfirmURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (a *Agreement) Resend() (*ResultV2, error) {
	a.build()
	a.sign()
	result := &ResultV2{kv: make(map[string]string)}
	undecoded, err := a.post(agreementSMSResendURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (a *Agreement) build() {
	v := a.params()
	v["meruserid"] = a.MeruserID
	v["accttype"] = a.AcctType
	v["acctno"] = a.AcctNO
	v["idno"] = a.IDNO
	v["acctname"] = a.AcctName
	v["mobile"] = a.Mobile
	v["validdate"] = a.ValidDate
	v["cvv2"] = a.Cvv2
}

type ResultV2 struct {
	kv        KV
	undecoded string
}

func (r *ResultV2) OK() bool {
	return r.kv["retcode"] == success && r.kv.verify()
}

func (r *ResultV2) TrxOK() bool {
	return r.OK() && r.TrxStatus() == "0000"
}

func (r *ResultV2) TrxStatus() string {
	return r.kv["trxstatus"]
}

func (r *ResultV2) RetMsg() string {
	return r.kv["retmsg"]
}

func (r *ResultV2) Undecoded() string {
	return r.undecoded
}

type AgreementApplyResult struct {
	*ResultV2
}

func (r *AgreementApplyResult) ErrMsg() string {
	return r.kv["errmsg"]
}

func (r *AgreementApplyResult) Thpinfo() string {
	return r.kv["thpinfo"]
}

type AgreementConfirmResult struct {
	*ResultV2
}

func (r *AgreementConfirmResult) ErrMsg() string {
	return r.kv["errmsg"]
}

func (r *AgreementConfirmResult) AgreeID() string {
	return r.kv["agreeid"]
}

func (r *AgreementConfirmResult) BankCode() string {
	return r.kv["bankcode"]
}

func (r *AgreementConfirmResult) BankName() string {
	return r.kv["bankname"]
}

type Payment struct {
	*ParamV2
	OrderID    string
	AgreeID    string
	Amount     string
	Currency   string
	Subject    string
	ValidTime  string
	TrxReserve string
	Thpinfo    string
	NotifyURL  string
}

func NewPayment() *Payment {
	return &Payment{
		Currency:  "CNY",
		ValidTime: "30",
		NotifyURL: paymentNotifyURL,
		ParamV2:   &ParamV2{kv: make(map[string]string)},
	}
}

func (p *Payment) Apply() (*PaymentResult, error) {
	p.build()
	p.sign()
	result := &PaymentResult{ResultV2: &ResultV2{kv: make(map[string]string)}}
	undecoded, err := p.post(paymentApplyURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (p *Payment) Confirm(smscode string) (*PaymentResult, error) {
	p.Currency = empty
	p.ValidTime = empty
	p.NotifyURL = empty

	p.build()
	if smscode != empty {
		p.kv["smscode"] = smscode
	}
	if p.Thpinfo != empty {
		p.kv["thpinfo"] = p.Thpinfo
	}
	p.sign()
	result := &PaymentResult{ResultV2: &ResultV2{kv: make(map[string]string)}}
	undecoded, err := p.post(paymentConfirmURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (p *Payment) Resend() (*ResultV2, error) {
	p.Currency = empty
	p.ValidTime = empty
	p.NotifyURL = empty

	p.build()
	if p.Thpinfo != empty {
		p.kv["thpinfo"] = p.Thpinfo
	}
	p.sign()
	result := &ResultV2{kv: make(map[string]string)}
	undecoded, err := p.post(paymentSMSResendURL, &result.kv)
	result.undecoded = undecoded
	return result, err
}

func (p *Payment) Accept(form url.Values) (bool, string) {
	p.kv.accept(form)
	return p.kv.verify() && p.kv["trxstatus"] == "0000", fmt.Sprintf("%v", form)
}

func (p *Payment) ID() string {
	return p.kv["cusorderid"]
}

func (p *Payment) TrxAmount() string {
	return p.kv["trxamt"]
}

func (p *Payment) build() {
	v := p.params()
	v["orderid"] = p.OrderID
	v["agreeid"] = p.AgreeID
	v["amount"] = p.Amount
	v["currency"] = p.Currency
	v["subject"] = p.Subject
	v["validtime"] = p.ValidTime
	v["trxreserve"] = p.TrxReserve
	v["notifyurl"] = p.NotifyURL
}

type PaymentResult struct {
	*ResultV2
}

func (p *PaymentResult) OrderID() string {
	return p.kv["orderid"]
}

func (p *PaymentResult) ErrMsg() string {
	return p.kv["errmsg"]
}

func (p *PaymentResult) TrxID() string {
	return p.kv["trxid"]
}

func (p *PaymentResult) ChnlTrxID() string {
	return p.kv["chnltrxid"]
}

func (p *PaymentResult) FinishTime() string {
	return p.kv["fintime"]
}

func (p *PaymentResult) Thpinfo() string {
	return p.kv["thpinfo"]
}

type PaymentNotify struct {
	kv        KV
	undecoded string
}

func NewPaymentNotify(form url.Values) *PaymentNotify {
	p := &PaymentNotify{
		kv:        make(map[string]string),
		undecoded: fmt.Sprintf("%v", form),
	}
	p.kv.accept(form)
	return p
}

func (p *PaymentNotify) OK() bool {
	return p.kv.verify()
}

func (p *PaymentNotify) Undecoded() string {
	return p.undecoded
}

func (p *PaymentNotify) OrderID() string {
	return p.kv["cusorderid"]
}

func do(url string, form url.Values, v interface{}) (string, error) {
	var undecoded string

	resp, err := http.PostForm(url, form)
	if err == nil {
		defer resp.Body.Close()
	}
	p, err := ioutil.ReadAll(resp.Body)
	if err == nil {
		undecoded = string(p)
		err = json.Unmarshal(p, v)
	}
	return undecoded, err
}
